#not working fine and pretty ugly in only one file for convenience and quick testing. Just a proof of concept
require 'rubygems'
require 'gosu'
require 'chipmunk'
include Gosu

SUBSTEPS, DTIME = 3, (1.0 / 30.0)

class Gosu::Song
  def safe_play(*args)
    play(*args) 
  end
  
  def safe_pause(*args)
    pause(*args) rescue nil
  end
  
  def safe_stop(*args)
    stop(*args) rescue nil
  end
end

class Gosu::Sample
  def safe_play(*args)
    play(*args) rescue nil
  end
end

class Gosu::Font
  def shadow_text(text,x,y,z,factor_x, factor_y,color)
    offset_x=0
    s_factor_x=factor_x * 1.3
    s_factor_y=factor_y * 1.2
    text.scan( /./ ) do |letter|
      draw(letter,x+offset_x,y,z,factor_x,factor_y,color)
      draw_rel(letter,x+offset_x,y,z-1,0.075,0.075,s_factor_x,s_factor_y,0xFF000000)
      
      offset_x +=text_width(letter, s_factor_x) + 3   
    end
  end
end

module Tiles
  Grass = 0
  Earth = 1
  Cloud = 2
end

class GemStone
  attr_reader :x, :y, :shape

  def initialize(image, x, y, shape)
    @image = image
    @x, @y = x, y
    @shape=shape
  end
  
  def draw(screen_x, screen_y)
    # Draw, slowly rotating
    @image.draw_rot(@x - screen_x, @y - screen_y, 0,
      15 * Math.sin(milliseconds / 150))
  end
end

# Player class.
class Player
  attr_reader  :score, :standing, :walk1, :walk2, :jump, :killed_img
  
BOX_SHAPE_ARRAY = [
  CP::Vec2.new(-25.0, -25.0),
  CP::Vec2.new(-25.0, 25.0),
  CP::Vec2.new(25.0, 25.0),
  CP::Vec2.new(25.0, -25.0)
]

  def reset_forces
    #@shape.body.reset_forces
  end
  
  def x
    @shape.body.p.x
  end
  def y
    @shape.body.p.y
  end
  def initialize(window,space)    
    @window=window
    @space=space
    @dir = :left
        
    # Load all animation frames
    @standing, @walk1, @walk2, @jump, @killed_img =
      *Image.load_tiles(window, "#{File.dirname(__FILE__)}/graphics/southpark_sprite.png", 50, 50, false)    
    @cur_image = @standing    
    @jump_sound= Gosu::Sample.new(window, "#{File.dirname(__FILE__)}/sound/swoosh.wav")
    @kill_sound= Gosu::Sample.new(window, "#{File.dirname(__FILE__)}/sound/wawawawa.wav")
    @player_font = Gosu::Font.new(window, "Arial Black", 60)
    @score_font = Gosu::Font.new(window, Gosu::default_font_name, 10)
    @score_sound= Gosu::Sample.new(window, "#{File.dirname(__FILE__)}/sound/yodel.wav")
    @score=0
    @lives=3
    @life_img=Image.new(window, "#{File.dirname(__FILE__)}/graphics/life_icon.png", false)
    @killed=false
    reset_shape
    
  end
  
  def reset_shape
    if @shape
      @space.remove_body @shape.body
      @space.remove_shape @shape
    end
    
    moment_of_inertia = CP.moment_for_poly(2.0, BOX_SHAPE_ARRAY, CP::Vec2.new(0,0))
    @body  = CP::Body.new(2.0, moment_of_inertia)
    @shape = CP::Shape::Poly.new(@body, BOX_SHAPE_ARRAY, CP::Vec2.new(0,0))
    #@shape.body.a = 0.0 #angle
    @shape.e = 0.7 #elasticity
    @shape.u = 0.5 #friction
    @shape.collision_type=:player
    @space.add_body(@body)
    @space.add_shape(@shape)
  end
  
  def kill    
    @window.set_state(:wait)
    @killed=true    
    @lives -= 1
    if @lives <= 0
      @window.set_game_over
    else
      @kill_sound.safe_play(1)
    end 
  end
  
  def reset(reset_lives=false)
    reset_shape
    @map = @window.map
    
    @lives=3 if reset_lives
    @killed=false    
    @shape.body.p=CP::Vec2.new(@map.player_x,@map.player_y)
  end
  
  def draw(screen_x, screen_y)    
    factor=1.0
    if @killed
      @cur_image=@killed_img            
      @player_font.shadow_text("EPIC FAIL!!", 50, 50, 1000, 1.5, 1.25, 0xEEFBCD2C)
      @player_font.draw("YOU LOST A LIFE!!", 10, 150, 1000, 1.25, 1.0, 0xFFFF0000)
      @player_font.draw("<SPACE> TO CONTINUE", 30, 230, 1000, 1.0, 1.0, 0xFFFF7900)
    end
  
    puts "v #{@shape.body.v} ::: i #{@shape.body.i} ::: f #{@shape.body.f} ::: a #{@shape.body.a} ::: w #{@shape.body.w} ::: t #{@shape.body.t} ::: "
    @cur_image.draw(@shape.body.p.x - 25 - screen_x, @shape.body.p.y - 25 -screen_y, 0, factor, 1.0)
    draw_score
  end
  
  def draw_score
    @lives.times do |i|
      @life_img.draw( 10 +  30 * i,  20, 1000,0.5,0.5)      
    end
    @score_font.draw("%06d" % @score, 10, 0, 1000, 3.0, 2.0, 0xCCffff00)
  end
  
  def move_right
    @shape.body.apply_impulse(CP::Vec2.new(2.0, 0.0), CP::Vec2.new(0, 0))  
  end
  
  def move_left
    @shape.body.apply_impulse(CP::Vec2.new(-2.0, 0.0), CP::Vec2.new(0, 0))  
  end
  
  def move_up
    if @shape.body.v.y.abs < 2
      @shape.body.apply_impulse(CP::Vec2.new(0.0, -250.0), CP::Vec2.new(0, 0))
      @jump_sound.safe_play
    end
  end


  def update
    return if @killed    
    v=@shape.body.v
    
    if (v.x.abs < 5)
      @cur_image = @standing
    else
      @cur_image = (milliseconds / 175 % 2 == 0) ? @walk1 : @walk2
    end
    if (v.y.abs >=2)
      @cur_image = @jump
    end
  end
  
  def collect_gem(gem_shape)  
    @score_sound.safe_play
    @window.map.gems.reject! do |c|
      if c.shape == gem_shape
        @score_sound.safe_play
        @score += 100
        @space.remove_body(gem_shape.body)
        @space.remove_shape(gem_shape)
        true
      else
        false
      end
    end
    if @window.map.gems.empty?
      @window.level_up
    end
  end
end

class Map
  attr_reader :width, :height, :gems, :filename, :player_x, :player_y, :shapes
  
  def initialize(window,space)
    @filename=File.join(window.level_dir,"map.txt")
    @tileset = Image.load_tiles(window, File.join(window.level_dir,'tileset.png'), 50, 50, true)
    @sky = Image.new(window, File.join(window.level_dir,'sky.png'), true)
    @gem_img = Image.new(window, "#{File.dirname(__FILE__)}/graphics/ruby_stone.png", false)
    @gems = []
    @window=window
    load_from_file(true,space)     
  end
  
  def load_from_file(draw_gems,space)
    static_body = CP::Body.new((1.0 / 0.0), (1.0 / 0.0));
    @shapes=[]
    lines = File.readlines(@filename).map { |line| line.chop }
      @height = lines.size
      @width = lines[0].size
      @tiles = Array.new(@width) do |x|
        Array.new(@height) do |y|
          case lines[y][x, 1].downcase
          when '"'
            points=[ CP::Vec2.new(x*50, y*50), CP::Vec2.new(x*50, (y+1) * 50), CP::Vec2.new((x+1)*50, (y+1)*50),CP::Vec2.new((x+1)*50, y*50) ]
            sp=CP::Shape::Poly.new(static_body, points, CP::Vec2.new(0,0))
            #sp=CP::Shape::Segment.new(static_body, CP::Vec2.new(0.0,   190), CP::Vec2.new(550, 190), 1.0)
            sp.e = 0.2 #elasticity
            sp.u = 0.6 #friction
            sp.collision_type=:wall    
            space.add_shape sp
            @shapes << sp
            Tiles::Grass
          when '#'            
            # borders
            points=[ CP::Vec2.new(x*50, y*50), CP::Vec2.new(x*50, (y+1) * 50), CP::Vec2.new((x+1)*50, (y+1)*50),CP::Vec2.new((x+1)*50, y*50) ]
            sp=CP::Shape::Poly.new(static_body, points, CP::Vec2.new(0,0))
            #sp=CP::Shape::Segment.new(static_body, CP::Vec2.new(0.0,   190), CP::Vec2.new(550, 190), 1.0)
            sp.e = 0.2 #elasticity    
            sp.collision_type=:wall    
            space.add_shape sp
            @shapes << sp
            Tiles::Earth
          when 'c'
            Tiles::Cloud  
          when 'x'
            points=[ CP::Vec2.new(x*50, y*50), CP::Vec2.new(x*50, ((y+1) * 50) - 5), CP::Vec2.new((x+1)*50, ((y+1)*50 - 5)),CP::Vec2.new((x+1)*50, y*50) ]
            sp=CP::Shape::Poly.new(static_body, points, CP::Vec2.new(0,0))
            sp.collision_type=:gem    
            space.add_shape sp
            @gems.push(GemStone.new(@gem_img, x * 50 + 25, y * 50 + 25,sp)) if draw_gems 
            nil
          when 'p'
             @player_x= (x +1 ) * 50 -1
             @player_y= (y +1 ) * 50 -1 
            nil
          else
            nil
          end
        end
      end

  end
  
  def draw(screen_x, screen_y)
    @sky.draw(0, 0, 0)
      
    first_x = screen_x / 50 
    first_y = screen_y / 50 
    last_x = first_x + 1 + 640 / 50
    last_y = first_y + 1 + 480 / 50
      
    (first_y..last_y).each do |y|
      (first_x..last_x).each do |x|
        break unless @tiles[x]      
        tile = @tiles[x][y]
        if tile
          @tileset[tile].draw(x * 50 - screen_x, y * 50 - screen_y, 0)      
        end
      end
    end
    @gems.each { |c| c.draw(screen_x, screen_y) }
  end
  
end

class Intro
  def initialize(window,player)
    @window
    @player=player
    @title_font = Gosu::Font.new(window, "croobie", 130) #jokewood, century gothic,chick, constantia 
    @text_font = Gosu::Font.new(window, "Arial Black", 100) #jokewood, century gothic,chick, constantia
    @factor_offset = 0
    @cur_intro_image = @player.standing
    @background = Image.new(window, "#{File.dirname(__FILE__)}/graphics/euruko_logo.png", true)
  end
  
  def update
    @cur_intro_image = case (milliseconds / 200 % 4 ) 
        when 0          
          @player.walk1
        when 1,3          
          @player.standing
        when 2          
          @player.walk2        
      end
      @factor_offset = (Math.sin(milliseconds / 1000.0) /2 )
  end
  
  def draw   
    factor = 2.0 + @factor_offset
    @cur_intro_image.draw(20,330,5000,factor,factor) 
    factor = 2.0 - @factor_offset
    @cur_intro_image.draw(620,330,5000,factor*-1,factor)
    
    @background.draw(0,0,0)
    
    factor=1.0
    factor -= 0.25 while(@title_font.text_width('EuRuKUEST',factor) > 620)    
    @title_font.shadow_text("EuRuKUEST", 20, 0, 1000, factor, factor, 0xFFB90000)
    factor -= 0.25 while(@text_font.text_width('INSERT',factor) > 300)
    @text_font.draw("INSERT", 180, 310, 1000, factor, factor, 0xFF568AC8)
    @text_font.draw("COIN", 200, 390, 1000, 1, 1, 0xFF93C3ED)
  end
end

class Game < Window
  attr_reader :map
  attr_accessor :score  
  
  STATES=[:play,:pause,:wait,:game_over,:intro,:level_up]
  MAX_LEVEL=2
  
  def initialize
    super(640, 480, false, 20)        
    self.caption = "EuRuKo 2009 BCN"
    @space = CP::Space.new
    @space.damping = 0.8#0.8
    @space.gravity = CP::Vec2.new(0.0, 20.0)
    @space.elastic_iterations=3
    @player = Player.new(self,@space)
    
    @space.add_collision_func(:player, :gem) do |player_shape, gem_shape|      
      @player.collect_gem(gem_shape)      
    end
    
    @gameover_font = Gosu::Font.new(self, 'chick', 200)
    @intro=Intro.new(self,@player)
    @level=1    
    set_state :intro
  end
  
  def level_dir
    File.join(File.dirname(__FILE__),'levels',@level.to_s)
  end
  
  def reset_level(reset_player_totally=false)
    unless @map.nil?
      @space.remove_body @map.shapes.first.body
      @map.shapes.each do |shape|
        @space.remove_shape shape
      end
    end
    @map = Map.new(self,@space)
    # Scrolling is stored as the position of the top left corner of the screen.
    @screen_x = @screen_y = 0    
    @player.reset(reset_player_totally)    
  end
  
  def set_state(to_state)
    state_from=@state
    @state=to_state if STATES.include? to_state
    curr_volume = @song.volume rescue 0.5
    
    if @state==:pause
        @song.safe_pause if @song     
    end
    
    case @state
      when :game_over
        @song.safe_stop if @song
        @song=Gosu::Song.new(self, "#{File.dirname(__FILE__)}/sound/Wingsjournal3.xm")
        @song.safe_play(true)
      when :intro      
        @song.safe_stop if @song        
        @song=Gosu::Song.new(self, "#{File.dirname(__FILE__)}/sound/telehueso.wav")
        @song.safe_play(true)
      when :level_up
        @song.safe_stop if @song
        @song=Gosu::Song.new(self, "#{File.dirname(__FILE__)}/sound/Wingsjournal1.xm")
        @song.safe_play(true)
    end
    
    @song.volume=curr_volume     
    
    return @state
  end
  
  def level_up
    if @level == MAX_LEVEL      
      #XXX should be game win text
      @level = 1
      reset_level
      set_state :level_up      
    else
      @level += 1
      reset_level
      set_state :level_up      
    end  
  end
  
  def get_state
    return @state || :play
  end
  
  def paused?; get_state==:pause; end
  def game_over?; get_state==:game_over; end
  def play?; get_state==:play; end
  def wait?; get_state==:wait; end
  def intro?; get_state==:intro; end
  def level_up?; get_state==:intro; end
  
  def toggle_pause
    state_from=get_state
    state_to=nil
    
    state_to=if state_from == :pause      
      set_state :play      
    elsif state_from == :play    
      set_state :pause
    end
    
    return state_to
  end
  
  
  def update
    
    if play?
      SUBSTEPS.times do
        @space.step(DTIME)      
        @player.reset_forces      
      end  
      
      @player.move_right  if button_down? Button::KbRight
      @player.move_left  if button_down? Button::KbLeft
      @player.update
      @screen_x = [[@player.x.to_i - 320, 0].max, @map.width * 50 - 640].min rescue 0
      @screen_y = [[@player.y.to_i - 240, 0].max, @map.height * 50 - 480].min rescue 0
      
    elsif intro?
      @intro.update        
    end 
  end
  
  def draw
    clip_to(0 , 0, 640, 480) do
       case get_state
         when :game_over
          draw_play_environment
          factor=1.0
          factor -= 0.25 while(@gameover_font.text_width('GAME',factor) > 440)
          @gameover_font.shadow_text("GAME", 100, 50, 1000, 1.0, 1.0, 0xF0FBCD2C)          
          @gameover_font.shadow_text("OVER", 100, 250, 1000, 1.0, 1.0, 0xF0CAA203)          
        when :play, :wait, :pause
          #draw_play_environment          
          puts "g #{@space.gravity}"
          @map.draw(@screen_x, @screen_y)
          @player.draw(@screen_x, @screen_y)
        when :level_up
          draw_play_environment
          factor=1.0
          factor -= 0.25 while(@gameover_font.text_width('LEVEL',factor) > 440)
          @gameover_font.shadow_text("LEVEL", 100, 50, 1000, 1.0, 1.0, 0xF0FBCD2C)          
          @gameover_font.shadow_text(@level.to_s, 300, 250, 1000, 1.0, 1.0, 0xF0CAA203)
        when :intro
          @intro.draw 
        end 
    end    
  end
  
  def draw_play_environment
    @map.draw @screen_x, @screen_y
    
  end
  
  def button_down(id)
    close if id == Button::KbEscape
    @song.volume=@song.volume - 0.1 if id == Button::KbPageDown && @song.volume > 0 
    @song.volume=@song.volume + 0.1 if id == Button::KbPageUp && @song.volume < 1
    
    case get_state
      when :game_over
        if id == Button::KbSpace
          set_state :intro
        end
      when :play
        @player.move_up if id == Button::KbUp        
        toggle_pause if id == Button::KbTab
      when :pause
        toggle_pause if id == Button::KbTab
      when :wait, :level_up
        if id == Button::KbSpace
          @player.reset 
          set_state :play
        end
      when :intro
        if id == Button::KbSpace
          reset_level(true)          
          set_state :level_up
        end
    end
  end
  
  def set_game_over
    Gosu::Sample.new(self, "#{File.dirname(__FILE__)}/sound/cartoon117_mouse_laughter.wav").safe_play(1)
    set_state :game_over
  end
end

Game.new.show
